/*
 * This file is Copyright 2002-2006 Loyola University Chicago,
 * Department of Computer Science and the Emerging Technologies Laboratory.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License via
 *    http://www.apache.org/licenses/LICENSE-2.0.
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.citep.formats.ofx;
import java.util.Hashtable;
import java.io.*;
import java.util.Date;
import java.util.GregorianCalendar;
import java.math.BigDecimal;


public class OFXParser extends SGMLParser {
	
	private SGMLParser strategy = null; //the current strategy we are using to parse this
	private OFXListener listener;
	
	public OFXParser(OFXListener l) {
		super(1);
		this.listener = l;
	}
	
	void unknownStartTag(String tag, Hashtable attributes) {
	}
	
	void startElement(String tag, Hashtable attributes) throws SGMLParserException,OFXParserException {
		tag = tag.toUpperCase();
		if(OFXLabels.STMTRS.equals(tag)) {
			//this is the beginning of a transaction, so we need start a new transaction object
			strategy = new StatementHandler(listener);
		}
		
		if(strategy != null)
			strategy.startElement(tag, attributes);
	}
	
	public void unknownEndTag(String tag) {		
	}
	
	public void handleEndTag(String tag) throws SGMLParserException {
		tag = tag.toUpperCase();

		if(strategy != null)
			strategy.handleEndTag(tag);
	}
	
	public void handleData(String data) {
		if(strategy != null)
			strategy.handleData(data);
	}

	protected void handleComment(String comment) {
		if(strategy != null)
			strategy.handleComment(comment);
	}
	
	protected void check(boolean condition, String msg) throws Exception {
		if(!condition)
			throw new Exception(msg);
		
	}
	
	protected Date parseOFXDate(String date) throws OFXParserException {
		if(date.length() != 14)
			throw new OFXParserException("Invalid date: " + date);

		
		
		int year = Integer.parseInt(date.substring(0, 4));
		int month = Integer.parseInt(date.substring(4,6));
		int day = Integer.parseInt(date.substring(6,8));
		int hour = Integer.parseInt(date.substring(8,10));
		int minute = Integer.parseInt(date.substring(10,12));
		int second = Integer.parseInt(date.substring(12,14));
		
		GregorianCalendar cal = new GregorianCalendar(year,month,day,hour,minute,second);
		return cal.getTime();
	}

	public void handleDocumentEnd() {
		if(listener != null)
			listener.onProcessingDone();
	}


	private class StatementHandler extends SGMLParser {
		private String previousTag = null;
		private String data = null;
		private OFXTransaction transaction = null;
		private OFXAccount account = null;
		
		private OFXStatement statement;;
		private OFXListener listener;
		
		public StatementHandler(OFXListener l) {
			super(1);
			this.listener = l;
			statement = new OFXStatement();
		}
		
		protected void startElement(String tag, Hashtable attributes) throws OFXParserException {
			if(OFXLabels.STMTTRN.equals(tag)) {
				transaction = new OFXTransaction(); //the current statement transaction
			} else if(OFXLabels.BANKACCTFROM.equals(tag)) {
				account = new OFXAccount();
			}
 
			
			//because there is no way of seeing when an element "ends" if it
			//has an open tag right now
			//we check here against the previous tag, which will "simulate"
			//an end element. Then we just put the data in the buffer into the 
			//transaction object				
			if (OFXLabels.TRNTYPE.equals(previousTag)) {
				handleEndTag(previousTag);
			} else if (OFXLabels.DTPOSTED.equals(previousTag)) {
				handleEndTag(previousTag);
			} else if (OFXLabels.TRNAMT.equals(previousTag)) {
				handleEndTag(previousTag);
			} else if (OFXLabels.FITID.equals(previousTag)) {
				handleEndTag(previousTag);
			} else if (OFXLabels.REFNUM.equals(previousTag)) {
				handleEndTag(previousTag);
			} else if (OFXLabels.NAME.equals(previousTag)) {
				handleEndTag(previousTag);
			} else if (OFXLabels.MEMO.equals(previousTag)) {
				handleEndTag(previousTag);
			} else if (OFXLabels.BANKID.equals(previousTag)) {
				handleEndTag(previousTag);
			} else if (OFXLabels.ACCTID.equals(previousTag)) {
				handleEndTag(previousTag);
			} else if (OFXLabels.ACCTTYPE.equals(previousTag)) {
				handleEndTag(previousTag);
			} 

			previousTag = tag;
		}
		
		public void handleData(String data) {
			this.data = data.trim();
		}

		public void handleEndTag(String tag) throws OFXParserException {
			
			if(OFXLabels.STMTTRN.equals(tag)) {
				statement.addTransaction(transaction);
			} else if(OFXLabels.STMTRS.equals(tag)) {
				listener.onOFXStatement(statement);
			} else if(OFXLabels.BANKACCTFROM.equals(tag)) {
				statement.setFromAccount(account);
			} 
					
			if(data != null && data.length() > 0) {			
				if (OFXLabels.TRNTYPE.equals(tag)) {
					transaction.setTransactionType(TransactionType.valueOf(data));
				} else if (OFXLabels.DTPOSTED.equals(tag)) {
					transaction.setDatePosted(parseOFXDate(data));
				} else if (OFXLabels.TRNAMT.equals(tag)) {
					transaction.setAmount(new BigDecimal(data));
				} else if (OFXLabels.FITID.equals(tag)) {
					transaction.setFi_id(data);
				} else if (OFXLabels.REFNUM.equals(tag)) {
					transaction.setReferenceNumber(data);
				} else if (OFXLabels.NAME.equals(tag)) {
					transaction.setName(data);
				} else if (OFXLabels.MEMO.equals(tag)) {
					transaction.setMemo(data);
				} else if (OFXLabels.BANKID.equals(previousTag)) {
					account.setBankId(data);
				} else if (OFXLabels.ACCTID.equals(previousTag)) {
					account.setAccountId(data);
				} else if (OFXLabels.ACCTTYPE.equals(previousTag)) {
					account.setAccountType(OFXAccount.AccountType.valueOf(data));
				} 
			}

			data = "";
		}
		
	}

	
	public static void main(String [] args) throws Exception {
		FileReader file = new FileReader("export.ofx");
		BufferedReader buf = new BufferedReader(file);
		StringBuilder input = new StringBuilder();
		
		String line;
		while((line = buf.readLine()) != null) {
			input.append(line);
		}

		OFXParser parser = new OFXParser(new OFXListener() {
			public void onOFXStatement(OFXStatement statement) {
				System.out.println("-------------- Account ------------");
				System.out.println("From account: " + statement.getFromAccount());
				
				System.out.println("-------------- Transactions ------------");
				for(OFXTransaction t:statement.getTransactions()) {
					System.out.println(t);
				}
			}
			
			public void onProcessingDone() {
				System.out.println("done!");
			}
		});
		
		parser.feed(input.toString());
		
	}
	
}
